package cc.shacocloud.mirage.bean.meta;

import cc.shacocloud.mirage.utils.ResolvableType;
import cc.shacocloud.mirage.utils.annotation.AnnotatedElementUtils;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Objects;

/**
 * 一个 Bean 对象的唯一键对象
 *
 * @author 思追(shaco)
 */
public class BeanKey {
    
    /**
     * 表示为任意类型
     */
    public static final ResolvableType ANY_TYPE = ResolvableType.NONE;
    
    /**
     * 限定符的值，用于标记注入点接受具有任何或没有限定符的 bean
     */
    public static final String ANY_QUALIFIER = "any";
    
    /**
     * 当前对象唯一键对应的类型
     *
     * @see ResolvableType
     */
    @Getter
    private final ResolvableType type;
    
    /**
     * Bean 的限定符 即 Bean name
     */
    @Nullable
    private final String qualifier;
    
    /**
     * 当该 Bean 作为依赖时使用，判断该依赖是否为必须的
     */
    @Getter
    private final boolean required;
    
    public BeanKey(@NotNull Class<?> beanClass) {
        this(beanClass, null);
    }
    
    public BeanKey(@NotNull ResolvableType type) {
        this(type, null);
    }
    
    public BeanKey(@NotNull Class<?> beanClass, @Nullable String qualifier) {
        this.type = ResolvableType.forClass(beanClass);
        this.qualifier = qualifier;
        this.required = true;
    }
    
    public BeanKey(@NotNull ResolvableType type, @Nullable String qualifier) {
        this.type = type;
        this.qualifier = qualifier;
        this.required = true;
    }
    
    public BeanKey(@NotNull ResolvableType type, @Nullable String qualifier, boolean required) {
        this.type = type;
        this.qualifier = qualifier;
        this.required = required;
    }
    
    /**
     * 获取此键的实际 Bean 类
     * <p>
     * 如果这是例如 {@link jakarta.inject.Provider}，那么这个 bean 类就是提供程序的泛型类型参数
     */
    @NotNull
    public Class<?> getBeanClass() {
        Class<?> rawClass = getType().getRawClass();
        return Objects.nonNull(rawClass) ? rawClass : Object.class;
    }
    
    /**
     * 获取此键的实际 Bean 类
     * <p>
     * 如果这是例如 {@link jakarta.inject.Provider}，那么这个 bean 类就是提供程序的泛型类型参数
     */
    public Type getBeanType() {
        return getType().getType();
    }
    
    /**
     * 如果这个 bean 键是用于 {@link jakarta.inject.Provider}
     *
     * @return 如果这是针对提供商的。对于默认实现为 false
     */
    public boolean isProvider() {
        return false;
    }
    
    /**
     * 如果此 Bean 可用于创建它所表示的 Bean 的正确实例
     *
     * @return 如果此键用于可以实例化的 Bean
     */
    public boolean isBeanForCreation() {
        return !isProvider();
    }
    
    /**
     * 获取此 Bean 键的限定符
     * <p>
     * 限定符与类型相结合有助于识别要注入的 Bean。有关限定符规则的详细信息，请参阅 {@link #canInject(BeanKey)}
     */
    @Nullable
    public String getQualifier() {
        return qualifier;
    }
    
    /**
     * 当前 bean 是否包含指定注解
     */
    public boolean hasAnnotation(Class<? extends Annotation> clazz) {
        return AnnotatedElementUtils.hasAnnotation(getBeanClass(), clazz);
    }
    
    /**
     * 获取当前 bean 包含的指定注解
     */
    @Nullable
    public <A extends Annotation> A getAnnotation(Class<A> clazz) {
        return AnnotatedElementUtils.getAnnotation(getBeanClass(), clazz);
    }
    
    /**
     * 如果此键表示标记为注入的字段或参数，是否可以注入 bean
     *
     * <p>规则：</p>
     * <ul>
     *   <li>Bean 必须属于相同类型或超类型。</li>
     *   <li>限定符必须相同，即使它为 <code>null<code></li>
     *   <li>除非类型是完全匹配的，并且此限定符为 <code>null<code></li>
     *   <li>或者这个限定符是<code>任意的<code></li>
     * </ul>
     */
    public boolean canInject(BeanKey bean) {
        if (bean == null) {
            return false;
        } else if (bean == this) {
            return true;
        }
        
        ResolvableType otherType = bean.getType();
        boolean matchType = getType().isAssignableFrom(otherType);
        
        if ((qualifier == null || isTheSameQualifier(qualifier, ANY_QUALIFIER)) && matchType) {
            return true;
        }
        
        // 空值则只判断限定符名称
        if (ANY_TYPE == getType() || matchType) {
            if (isTheSameQualifier(qualifier, bean.getQualifier())) {
                return true;
            }
            return hasTheAnyQualifier();
        }
        
        return false;
    }
    
    /**
     * 如果此键表示标记为注入的字段或参数，是否可以注入 Bean（如果由工厂创建）
     *
     * <p>
     * 与 {@link #canInject(BeanKey)} 相同的规则适用。
     * 但有一个例外，工厂还可以指定 <code>any<code> 限定符，以便为匹配类型的任何注射点创建 bean
     */
    public boolean canInjectFromFactory(BeanKey factoryCreatedBean) {
        if (factoryCreatedBean == null) {
            return false;
        }
        
        if (canInject(factoryCreatedBean)) {
            return true;
        }
        
        ResolvableType otherType = factoryCreatedBean.getType();
        return otherType.isAssignableFrom(getType())
                && (factoryCreatedBean.hasTheAnyQualifier() || hasTheAnyQualifier() || isTheSameQualifier(qualifier, factoryCreatedBean.getQualifier()));
    }
    
    /**
     * 检查此 Bean 键是否具有 <code>any<code> 限定符
     */
    public boolean hasTheAnyQualifier() {
        return isTheSameQualifier(qualifier, ANY_QUALIFIER);
    }
    
    /**
     * 判断限定符是否相同 忽略大小写
     */
    private boolean isTheSameQualifier(String actualQualifier, String expectedQualifier) {
        if (actualQualifier == null && expectedQualifier == null) {
            return true;
        }
        
        return actualQualifier != null && actualQualifier.equalsIgnoreCase(expectedQualifier);
    }
    
    /**
     * 检查基于 {@link #getType()} 的相等性。
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        }
        
        if (!(obj instanceof BeanKey)) {
            return false;
        }
        
        final BeanKey beanKey = (BeanKey) obj;
        
        if (getType().equals(beanKey.getType())) {
            if (qualifier == null && beanKey.getQualifier() == null) {
                return true;
            } else return isTheSameQualifier(qualifier, beanKey.getQualifier());
        }
        
        return false;
    }
    
    @Override
    public int hashCode() {
        if (Objects.nonNull(qualifier)) {
            return getType().hashCode() + Objects.requireNonNull(qualifier).toLowerCase().hashCode();
        } else {
            return getType().hashCode();
        }
    }
    
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        
        if (qualifier != null) {
            builder.append("对象名称=").append(qualifier).append(" ");
        }
        
        builder.append("对象类型=").append(type.toString());
        
        return builder.toString();
    }
    
}
